# /**
# * Copyright (c) 2022 Beijing Jiaotong University
# * PhotLab is licensed under [Open Source License].
# * You can use this software according to the terms and conditions of the [Open Source License].
# *
# * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
# *
# * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# *
# * See the [Open Source License] for more details.
# */
# /**
# * Author: Shihao Xie
# * Created: Mar. 22, 2022
# * Supported by: National Key Research and Development Program of China
# */
import os.path
from typing import Any

from phot_server.rpc.device_data_type.ttypes import PhotGrid, PhotSource
from phot_server.server_config.global_config import TEMP_ROOT
import photfdtd
import inspect
import copy
from phot_server.repository.material_repository import MaterialRepository
from photfdtd.index import Index
import numpy as np
from json import dumps, loads


class GridFactory:
    def __init__(self):
        self.waveguide_class = self.__gen_waveguide_class_dict()
        self.grid_class = self.__gen_grid_class()
        self.set_source_parameters = self.__get_class_method_parameters(self.grid_class, "set_source")
        self.set_detector_parameters = self.__get_class_method_parameters(self.grid_class, "set_detector")
        self.__grid_parameter_map = {
            "xLength": "grid_xlength",
            "yLength": "grid_ylength",
            "zLength": "grid_zlength",
            "gridSpacing": "grid_spacing",
            "totalTime": "total_time",
            # "pmlWidthX": "pml_width_x",
            # "pmlWidthY": "pml_width_y",
            # "pmlWidthZ": "pml_width_z",
            "permittivity": "permittivity",
            "permeability": "permeability",
            "courantNumber": "courant_number",
            "foldername": "foldername",
        }
        self.__source_parameter_map = {
            "sourceType": "source_type",
            "period": "period",
            "waveLength": "wavelength",
            "amplitude": "amplitude",
            "phaseShift": "phase_shift",
            "name": "name",
            "pulse": "pulse",
            "cycle": "cycle",
            "hanningDt": "hanning_dt",
            "polarization": "polarization",
            "x": "x",
            "y": "y",
            "z": "z",
            "xLength": "xlength",
            "yLength": "ylength",
            "zLength": "zlength"
        }
        self.__waveguide_parameter_map = {
            "xLength": "xlength",
            "yLength": "ylength",
            "zLength": "zlength",
            "x": "x",
            "y": "y",
            "z": "z",
            "width": "width",
            "name": "name",
            "refractiveIndex": "refractive_index",
            "direction": "direction",
            "xlengthWaveguide": "xlength_waveguide",
            "xlengthTaper": "xlength_taper",
            "ylengthTaper": "ylength_taper",
            "width_sbend": "widthSbend",
            "gap": "gap",
            "outerRadius": "outer_radius",
            "We": "We",
            "widthPort": "width_port",
            "widthWg": "width_wg",
            "ln": "ln",
            "lm": "lm",
            "lPort": "l_port",
            "length": "length",
            "radius": "radius",
            "gapDc": "gap_dc",
            "xlengthDc": "xlength_dc",
            "coupleLength": "couplelength",
            "coupleLengthDc": "couplelength_dc",
            "addLengthArm1": "addlength_arm1",
            "addLengthArm2": "addlength_arm2",
            "angle_phi": "angle_phi",
            "angle_psi": "angle_psi"
        }
        self.__detector_parameter_map = {
            "xLength": "xlength",
            "yLength": "ylength",
            "zLength": "zlength",
            "x": "x",
            "y": "y",
            "z": "z",
            "name": "name",
            "detectorType": "detector_type",
        }

    def __parameter_to_attributes(self, obj):
        if obj.parameters is None and obj.parameters != "":
            return
        parameters = loads(obj.parameters)
        if not isinstance(parameters, dict):
            return
        # name: str = obj.__class__.__name__
        # reversed_map = {v: k for k, v in map.items()}
        for key, value in parameters.items():
            # if key in reversed_map.keys():
            #     attribute_name = reversed_map[key]
            #     setattr(obj, attribute_name, value)
            if hasattr(obj, key) and value is not None:
                setattr(obj, key, value)


    def create_grid_object(self, grid: PhotGrid):
        grid_class = self.grid_class
        init_parameters_name_list = self.__get_class_method_parameters(grid_class, "__init__")
        self.__parameter_to_attributes(grid)
        parameters = {}
        parameters.update(grid.__dict__)
        parameters.update(loads(grid.parameters if grid.parameters is not None else "{}"))
        init_parameters_dict = self.__filter_parameter(init_parameters_name_list, parameters,
                                                       self.__grid_parameter_map)
        if init_parameters_dict.get("foldername" == None):
            init_parameters_dict["foldername"] = os.path.join(TEMP_ROOT, grid.name)
        # init_parameters_dict.update(parameters)
        # init_parameters_dict["grid_spacing"] *= 1e-6
        grid_instance = grid_class(**init_parameters_dict)

        if grid.waveLength is not None:
            wave_length = grid.waveLength
        elif parameters.get("wave_length", None) is not None:
            wave_length = parameters.get("wave_length", None)
        else:
            wave_length = 1550 * 1e-9

        material_repository = MaterialRepository()
        index_fit_list = {}
        for waveguide in grid.waveguides:
            self.__parameter_to_attributes(waveguide)
            if waveguide.material is not None:
                refractive_index_fit = index_fit_list.get(waveguide.material.name, None)
                refractive_index = material_repository.get_material_refractive_index_by_wave_length(
                    waveguide.material.name, wave_length*1e6)
                if refractive_index is None and refractive_index_fit is None:
                    data = []
                    wl = []
                    re = []
                    im = []
                    for material_property in waveguide.material.properties:
                        wl.append(material_property.waveLength)
                        re.append(material_property.real)
                        im.append(material_property.imag)
                    data.append(wl)
                    data.append(re)
                    data.append(wl)
                    data.append(im)

                    index_fit = Index(data=np.array(data).T)
                    index_fit.fit()
                    index_fit_list[waveguide.material.name] = index_fit
                    refractive_index = index_fit_list[waveguide.material.name].get_refractive_index(wave_length)
                waveguide.refractiveIndex = refractive_index[0]
            fdtd_waveguide = self.create_waveguide_object(waveguide, grid_instance)
            grid_instance.add_object(fdtd_waveguide)


        sources = copy.deepcopy(grid.sources)
        if grid.waveguides is not None and isinstance(grid.waveguides, list) and len(grid.waveguides) > 0:
            waveguide_centre_x = int(np.average([waveguide.x for waveguide in grid.waveguides]))
            waveguide_centre_y = int(np.average([waveguide.y for waveguide in grid.waveguides]))
            waveguide_centre_z = int(np.average([waveguide.z for waveguide in grid.waveguides]))
            if sources is None or (isinstance(sources, list) and len(sources) == 0):
                sources.append(PhotSource(name="default_source", waveLength=wave_length, sourceType="pointsource",
                                          x=waveguide_centre_x, y=waveguide_centre_y, z=waveguide_centre_z,
                                          xLength=0, yLength=0, zLength=0))
        for source in sources:
            self.__parameter_to_attributes(source)
            source_parameters = self.__filter_parameter(self.set_source_parameters, source.__dict__,
                                                        self.__source_parameter_map)
            source_parameters.update(loads(source.parameters if source.parameters is not None else "{}"))
            # source_parameters["wavelength"] = source_parameters["wavelength"] * 1e-9
            grid_instance.set_source(**source_parameters)
            # wave_length = source_parameters["wavelength"] * 1e6

        for detector in grid.detectors:
            self.__parameter_to_attributes(detector)
            detector_parameters = self.__filter_parameter(self.set_detector_parameters, detector.__dict__,
                                                          self.__detector_parameter_map)
            detector_parameters.update(loads(detector.parameters if detector.parameters is not None else "{}"))
            grid_instance.set_detector(**detector_parameters)
        return grid_instance

    def create_waveguide_object(self, waveguide, grid):
        waveguide_class = self.waveguide_class[waveguide.className]
        init_parameters_name_list = self.__get_class_method_parameters(waveguide_class, "__init__")
        waveguide_dict: dict = copy.deepcopy(waveguide.__dict__)
        for key, value in loads(waveguide_dict.get("parameters", "{}")).items():
            if value is not None:
                waveguide_dict.update({key: value})
        init_parameters_dict = self.__filter_parameter(init_parameters_name_list, waveguide_dict,
                                                       self.__waveguide_parameter_map)

        # if waveguide.extraParameters is not None:
        #     init_parameters_dict.update(waveguide.extraParameters)
        init_parameters_dict.update({"grid": grid})
        waveguide_instance = waveguide_class(**init_parameters_dict)
        return waveguide_instance

    def __gen_waveguide_class_dict(self) -> dict[str, Any]:
        cls_dict = {}
        for cls_name, fdtd_cls in inspect.getmembers(photfdtd, inspect.isclass):
            if fdtd_cls.__bases__[0].__name__ == "Waveguide" or fdtd_cls.__name__ == "Waveguide":
                cls_dict.update({cls_name: fdtd_cls})
        return cls_dict

    def __gen_grid_class(self):
        for cls_name, fdtd_cls in inspect.getmembers(photfdtd, inspect.isclass):
            if fdtd_cls.__name__ == "Grid":
                return fdtd_cls
        return None

    def __get_class_method_parameters(self, class_pointer, method_name):
        method_pointer = getattr(class_pointer, method_name)
        parameter_name_dict = {}
        for parameter in inspect.signature(method_pointer).parameters.values():
            if parameter.name != "self":
                parameter_name_dict[parameter.name] = parameter.annotation.__name__
        return parameter_name_dict

    def __filter_parameter(self, refer_dict: dict, parameter_dict: dict, parameter_mapping: dict) -> dict:
        filtered_paremters = dict()
        for parameter_name, parameter_value in parameter_dict.items():
            parameter = parameter_mapping.get(parameter_name, parameter_name)
            if parameter in refer_dict.keys() and parameter_value is not None:
                filtered_paremters.update(
                    {parameter: self.__type_cast(parameter_value, refer_dict[parameter])})
        return filtered_paremters

    def __type_cast(self, value, cast_type):
        if value is None:
            return None
        if cast_type == "float":
            return float(value)
        elif cast_type == "int":
            return int(value)
        elif cast_type == "str":
            return str(value)
        elif cast_type == "bool":
            return bool(value)
        else:
            return value
